import os
from tkinter import *
from threading import Timer
import random


# 函数代码
def print_map():
    global image_map
    for x in range(0, Width):
        for y in range(0, Height):
            if(map[x][y] != ' '):
                img1 = imgs[int(map[x][y])]
                id = cv.create_image((x*40 + 20, y*40 + 20), image=img1)
                image_map[x][y] = id

    cv.pack()
    for y in range(0, Height):
        for x in range(0, Width):
            print(map[x][y], end=' ')
        print(',', y)


def create_map( ):#产生map地图
    global map
    #生成随机地图
    #将所有匹配成对的动物物种放进一个临时的地图中
    tmpMap = []
    m=(Width)*(Height)//10
    print('m=',m)
    for x in range(0,m):
       for i in range(0,10):# 每种方块有10个
          tmpMap.append(x)
    random.shuffle(tmpMap)
    for x in range(0,Width):#0--14 
       for y in range(0,Height):#0--14
           map[x][y]=tmpMap[x*Height+y]



# 鼠标左键事件代码
def callback(event):
    global Select_first, p1, p2
    global firstSelectRectId, SecondSelectRectId
    # print("clicked at", event.x, event.y, turn)
    x = (event.x) // 40     # 取余
    y = (event.y) // 40

    print('clicked at', x, y)

    if map[x][y] == ' ':
        showinfo(title='提示', message='此处无方块')
    else:
        if Select_first == False:
            p1 = Point(x, y)
            # 画选定出的线框
            firstSelectRectId = cv.create_rectangle(x*40, y*40, x*40+40, y*40+40, outline='blue')
            Select_first = True
        else:
            p2 = Point(x, y)
            # 判断第二次单击的放开是否已被第一次单击选取，如果是则返回
            if (p1.x == p2.x) and (p1.y == p2.y):
                return
            print('第二次单机', x, y)
            SecondSelectRectId = cv.create_rectangle(x*40, y*40, x*40+40, y*40+40, outline='yellow')
            print('第二次点方块', SecondSelectRectId)
            cv.pack()

            if IsSame(p1, p2) and IsLink(p1, p2):  # 判断是否联通
                print('连通', x, y)
                Select_first = False
                # 话选中方块之间的连接线
                drawLinkLine(p1, p2)
                t = Timer(timer_interval, delayrun)  # 定时函数
                t.start()
            else:
                cv.delete(firstSelectRectId)  # 不能联通则取消选定的两个方块
                cv.delete(SecondSelectRectId)
                Select_first = False


# 判断是否相同
def IsSame(p1, p2):
    if map[p1.x][p1.y] == map[p2.x][p2.y]:
        return True
    return False

'''
判断选中的两个方块是否可以消除
'''
def IsLink(p1,p2): 
    if lineCheck(p1, p2):
        return True    
    if  OneCornerLink(p1, p2):      #一个转弯（折点）的联通方式
        return True
    if  TwoCornerLink(p1, p2):      #两个转弯（折点）的联通方式
        return True
    return False

'''
* 同行同列情况消除方法 原理：如果两个相同的被消除元素之间的 空格数
spaceCount等于他们的（行/列差-1）则 两者可以联通消除
* x代表列，y代表行
* param p1 第一个保存上次选中点坐标的点对象
* param p2 第二个保存上次选中点坐标的点对象
'''

#直接连通
def lineCheck(p1, p2):
    absDistance = 0 
    spaceCount = 0
    if (p1.x == p2.x or p1.y == p2.y) : # 同行同列的情况吗？
       print("同行同列的情况------")
       #同列的情况
       if (p1.x == p2.x and p1.y != p2.y) :
           print("同列的情况")
           #绝对距离(中间隔着的空格数)
           absDistance = abs(p1.y - p2.y) - 1
           #正负值
           if  p1.y - p2.y > 0 :
               zf=-1
           else:
               zf=1
           for i in range(1,absDistance+1):
               if (map[p1.x][p1.y + i * zf]==" "):
                   # 空格数加1
                   spaceCount += 1
               else:
                   break;#遇到阻碍就不用再探测了
            
       #同行的情况
       elif (p1.y == p2.y and p1.x != p2.x):
            print(" 同行的情况")
            absDistance = abs(p1.x - p2.x) - 1
            #正负值
            if  p1.x - p2.x > 0 :
               zf=-1
            else:
               zf=1
            for i in range(1,absDistance+1):
                if (map[p1.x + i * zf][p1.y]==" "):
                    # 空格数加1
                    spaceCount += 1
                else:
                    break;#遇到阻碍就不用再探测了
       if (spaceCount == absDistance) :
           #可联通
           print(absDistance,spaceCount)
           print("行/列可直接联通")
           return True
       else:
           print("行/列不能消除！")
           return False
    else:
       #不是同行同列的情况所以直接返回false
       return False; 
   
#--------------------------------------
#第二种，直角连通
'''
直角连接，即X,Y坐标都不同的，可以用这个方法尝试连接
 param first:选中的第一个点
 param second:选中的第二个点
'''
def OneCornerLink(p1, p2):
    #第一个直角检查点，如果这里为空则赋予相同值供检查
    checkP = Point(p1.x, p2.y)
    #第二个直角检查点，如果这里为空则赋予相同值供检查
    checkP2 = Point(p2.x, p1.y);
    #第一个直角点检测
    if (map[checkP.x][checkP.y]==" "):
        if (lineCheck(p1, checkP) and lineCheck(checkP, p2)):
            linePointStack.append(checkP)
            print("直角消除ok",checkP.x,checkP.y)
            return True
    #第二个直角点检测
    if (map[checkP2.x][checkP2.y]==" "):
        if (lineCheck(p1, checkP2) and lineCheck(checkP2, p2)):
            linePointStack.append(checkP2)
            print("直角消除ok",checkP2.x,checkP2.y)
            return True
    print("不能直角消除" )
    return False;
#-----------------------------------------
'''
#第三种，双直角连通
双直角联通判定可分两步走：
1. 在p1点周围4个方向寻找空格checkP
2. 调用OneCornerLink(checkP, p2)
3. 即遍历 p1 4 个方向的空格，使之成为 checkP,然后调用 OneCornerLink(checkP, 
p2)判定是否为真，如果为真则可以双直角连同，否则当所有的空格都遍历完而没有找
到一个checkP使OneCornerLink(checkP, p2)为真，则两点不能连同
具体代码：

双直角连接方法
@param p1 第一个点
@param p2 第二个点
'''
def TwoCornerLink(p1, p2):
    checkP = Point(p1.x, p1.y)
    #四向探测开始
    for i in range(0,4):
        checkP.x=p1.x
        checkP.y=p1.y
        # 向下
        if (i == 3):
           checkP.y+=1
           while (( checkP.y < Height) and  map[checkP.x][checkP.y]==" "):
               linePointStack.append(checkP)
               if (OneCornerLink(checkP, p2)):
                  print("下探测OK")
                  return True
               else:
                  linePointStack.pop()
               checkP.y+=1
           print ("ssss",checkP.y,Height-1)
           #补充两个折点都在游戏区域底侧外部
           if checkP.y==Height:  #出了底部，则仅需判断p2能否也达到底部边界
               z=Point(p2.x, Height-1)    #底部边界点            
               if lineCheck(z,p2) : #两个折点在区域外部的底侧
                   linePointStack.append(Point(p1.x, Height))
                   linePointStack.append(Point(p2.x, Height))
                   print("下探测到游戏区域外部OK")
                   return True
        # 向右
        elif (i == 2):
           checkP.x+=1
           while (( checkP.x < Width) and  map[checkP.x][checkP.y]==" "):
               linePointStack.append(checkP)
               if (OneCornerLink(checkP, p2)):
                  print("右探测OK")
                  return True
               else:
                  linePointStack.pop()
               checkP.x+=1
           #补充两个折点都在游戏区域右侧外部
           if checkP.x==Width:  #出了右侧，则仅需判断p2能否也达到右部边界
               z=Point(Width-1,p2.y)   #右部边界点            
               if lineCheck(z,p2) : #两个折点在区域外部的底侧
                   linePointStack.append(Point(Width,p1.y))
                   linePointStack.append(Point(Width,p2.y))
                   print("右探测到游戏区域外部OK")
                   return True
        # 向左
        elif (i == 1):
           checkP.x-=1
           while (( checkP.x >=0) and  map[checkP.x][checkP.y]==" "):
               linePointStack.append(checkP)
               if (OneCornerLink(checkP, p2)):
                  print("左探测OK")
                  return True
               else:
                  linePointStack.pop()
               checkP.x-=1
        # 向上
        elif (i == 0):
           checkP.y -=1 
           while ((checkP.y >=0) and  map[checkP.x][checkP.y]==" "):
               linePointStack.append(checkP)
               if (OneCornerLink(checkP, p2)):
                  print("上探测OK")
                  return True
               else:
                  linePointStack.pop()
               checkP.y-=1
        
    #四个方向都寻完都没找到适合的checkP点
    print( "两直角连接没找到适合的checkP点")
    return False;




# 画连接线
def drawLinkLine(p1, p2):
    if(len(linePointStack) == 0):
        Line_id.append(drawLine(p1, p2))
    else:
        print(linePointStack, len(linePointStack))

    if(len(linePointStack) == 1):
        z = linePointStack.pop()
        print('折通点1', z.x, z.y)
        Line_id.append(drawLine(p1, z))
        Line_id.append(drawLine(p2, z))
    if(len(linePointStack) == 2):
        z1 = linePointStack.pop()
        print("折通点2-1", z1.x, z1.y)
        Line_id.append(drawLine(p2, z1))
        z2 = linePointStack.pop()
        print('折通点2-2', z2.x, z2.y)
        Line_id.append(drawLine(z1, z2))
        Line_id.append(drawLine(p1, z2))


# 画两点之间的线
def drawLine(p1, p2):
    print('drawLine p1, p2', p1.x, p1.y, p2.x, p2.y)
    id = cv.create_line(p1.x*40+20, p1.y*40+20, p2.x*40+20, p2.y*40+20, width=5, fill='red')
    # cv.pack()
    return id


# 删除连接线
def undrawConnectLine():
    while len(Line_id) > 0:
        idpop = Line_id.pop()
        cv.delete(idpop)


def delayrun():
    clearTwoBlock()


# 清楚方块
def clearTwoBlock():
    # 清除两个选定框线
    cv.delete(firstSelectRectId)
    cv.delete(SecondSelectRectId)

    # 清空记录方块的值
    map[p1.x][p1.y] = ' '
    cv.delete(image_map[p1.x][p1.y])
    map[p2.x][p2.y] = ' '
    cv.delete(image_map[p2.x][p2.y])

    Select_first = False
    undrawConnectLine()


def IsWin():
    # 非 BLANK_STATE 状态
    for y in range(0, Height):
        for x in range(0, Width):
            if map[i] != ' ':
                return False
    return True


def find2Block(event):#自动查找
    global firstSelectRectId,SecondSelectRectId
    m_nRoW=Height
    m_nCol=Width
    bFound = False;
    #第一个方块从地图的0位置开始
    for i in range(0, m_nRoW* m_nCol):
        #找到则跳出循环
        if (bFound):
            break
        
        #算出对应的虚拟行列位置
        x1 = i % m_nCol 
        y1 = i // m_nCol
        p1=Point(x1,y1)
        #无图案的方块跳过
        if (map[x1][y1] == ' '):
            continue
        #第二个方块从前一个方块的后面开始
        for j in range( i +1 , m_nRoW* m_nCol):
            #算出对应的虚拟行列位置
            x2 = j % m_nCol 
            y2 = j // m_nCol                
            p2=Point(x2,y2)
            # 第二个方块不为空 且与第一个方块的动物相同
            if (map[x2][y2] != ' ' and IsSame(p1,p2)):
                #判断是否可以连通
                if (IsLink(p1, p2)):
                    bFound = True
                    break
    #找到后自动消除
    if (bFound):  #p1（x1,y1）与p2（x2,y2）连通
        print('找到后',p1.x,p1.y,p2.x,p2.y)
        #画选定（x1,y1)处的框线
        firstSelectRectId=cv.create_rectangle(x1*40,y1*40,x1*40+40,y1*40+40,width=2,outline="red")
        #画选定（x2,y2)处的框线
        secondSelectRectId=cv.create_rectangle(x2*40,y2*40,x2*40+40,y2*40+40,width=2,outline="red")
        #t=Timer(timer_interval,delayrun)#定时函数
        #t.start() 

    return bFound 


# 主逻辑
class Point:    # 点类
    def __init__(self, x, y):
        self.x = x
        self.y = y


if __name__ == "__main__":
    root = Tk()
    root.title('python 连连看')

    # 延时消除
    timer_interval = 0.3

    imgs = os.listdir('gif')

    print(imgs)

    imgs = [PhotoImage(file='gif/' + i) for i in imgs]

    Select_first = False                                        # 是否已经选中第一块

    FirstSelectRectId = -1                                      # 选中对象1

    SecondSelectRectId = -1                                     # 选中对象2

    linePointStack= []
    Line_id = []
    Height = 9
    Width = 10

    map = [[" " for y in range(Height)] for x in range(Width)]

    image_map = [[" " for y in range(Height)] for x in range(Width)]

    cv = Canvas(root, bg='green', width=610, height=610)

    cv.bind("<Button-1>", callback)

    cv.bind("<Button-3>", find2Block)

    cv.pack()

    create_map()

    print_map()

    root.mainloop()


